# This isn't the file you're supposed to run to start the file, just
# the file for the "game" object, which can be serialized.

# Filling out this, I'm getting the feeling that I'm "doing it wrong",
# to use the internet vernacular. At least it'll be secure.


from rlsdl import *
from rlcore import *
from erlais import *
from erlitem import *
from erlrays import *
from erlserial import *
from erldevice import *
from erlglobals import *
from rlinterface import *
from erlmaininput import *
from erlcomponents import *

from gzip import GzipFile

import random

class TileBrushes:
	serializedAttributes = [
		"rock", "floor", "wall", "closedDoor", "openDoor", "door",
		"corridor", "stairsUp", "stairsDown",
		"brokenDoor"
	]
	def __init__(self):
		self.rock = TileType( "rock", "impassable", "opaque", Glyph( ' ', (0,0,0), (60,50,40) ) )

		self.floor = TileType( "cave floor", "land", "clear", Glyph( ' ', (0,0,0), (150,150,100) ) )
		self.stairsDown = TileType( "stairs down", "land", "clear", Glyph( '>', (196,196,196), (150,150,100) ) )
		self.stairsUp = TileType( "stairs up", "land", "clear", Glyph( '<', (196,196,196), (150,150,100) ) )

		self.wall = TileType( "cave wall", "impassable", "opaque", Glyph( ' ', (0,0,0), (220,160,160) ) )
		self.closedDoor = self.door = TileType( "closed door", "closed-door", "blocks", Glyph( '+', (0,0,0), (80,80,80) ) )
		self.openDoor = TileType( "open door", "open-door", "clear", Glyph( '-', (0,0,0), (80,80,80) ) )
		self.corridor = TileType( "corridor", "land", "clear", Glyph( ' ', (0,0,0), (150,100,100) ) )
		self.brokenDoor = TileType( "broken door", "open-door", "clear", Glyph( 'x', (0,0,0), (80,80,80) ) )

class PlayerCharacter (Mobile):
	serializedAttributes = Mobile.serializedAttributes + [
		"readiedItem",
		"paintHighlights",
		"game",
		"inventions",
		"knownComponents",
		"knownChassises",
		"robotsExamined",
		"unknownComponents"
	]
	def __init__(self, name, level, game):
		Mobile.__init__(self, name,
			Glyph('@', (255,255,255), None ),
			standardTraversibility,
			seethrough = [ "clear" ],
			maxHp = 100,
		)
		self.paintHighlights = True
		self.game = game
		self.viewport = game.interface.elements["map"]
		self.readiedItem = None
		self.vision = MobileVision( self, radius = None )
		game.interface.elements["info"].player = self
		x, y = level.getRandomTileOfType( self.game.gtt.stairsUp )
		self.move( level, x , y )

		wrench = ItemTable.Wrench.spawn()
		returned, wrench = self.inventory.add( wrench )
		self.readyItem( wrench, silent = True)
		mediKit = ItemTable.MediKit.spawn(3)
		returned, medikit = self.inventory.add( mediKit )
		self.inventory.weightLimit = pcCarryingCapacity
		self.inventory.slotLimit = len( SlotNames )
		self.inventions = []
		self.robotsExamined = []
		self.unknownComponents = [
			[ cLineProjector, cHeatPrism ],
			[ cSignalDelay, cFreezePrism, cBomb, cBallProjector ],
			[ cAmplifierPrism, cWideProjector, cBurstProjector ]
		]
		self.knownComponents = [
			cCoalChamber, cCoalEngine, cTrigger
		]
		self.knownChassises = [ chassisHandheld ]
	def chooseDiscovery(self):
		rv = []
		k = len(self.unknownComponents)
		for i in range(k):
			for j in range( (k-i)*(k-i) ):
				for elt in self.unknownComponents[i]:
					if not elt in self.knownComponents:
						rv.append( elt )
		if not rv: return None
		print rv
		return random.choice( rv )
	def examineRobot(self, newRobot): #nr might just be a string...
		msg = self.game.interface.messages.add
		if self.robotsExamined.count( newRobot ) > 2:
			msg( "You already know all there is to know about this model." )
		else:
			self.robotsExamined.append( newRobot )
			discovery = self.chooseDiscovery()
			if not discovery:
				msg( "This technology is too primitive to be worthy of your attention." )
			else:
				msg( "You study the remains of the robot. You learn how to construct a new component: \"%s\"!" % discovery.componentName )
				self.knownComponents.append( discovery )
	def pickup(self, item):
		techCore = getattr( item, "technologyCore", None )
		if techCore:
			self.examineRobot( techCore )
		else:
			try:
				if item.amount > 1 and not item.itemType.unit:
					targets = item.itemType.name.plural
				else:
					targets = item.itemType.name.singular
				returned, taken = self.inventory.add( item )
				if returned:
					self.level.putItem( returned, self.x, self.y )
				if taken and not returned:
					self.game.interface.messages.add( "You pick up the %s." % targets )
					return True
				elif taken:
					self.game.interface.messages.add( "You're can't carry it all -- you pick up %s, leaving the rest behind." % taken.description() )
					return True
				else:
					self.game.interface.messages.add( "You're already overburdened -- you can't pick up the %s without leaving behind something else." % targets )
					return False
			except NoMoreSlots:
				self.game.interface.messages.add( "You've run out of pockets!" )
				return False
	def didMove(self):
		self.game.interface.elements["map"].mobile = self # bit of a hack
		self.generateFov()
		#self.game.interface.refresh()
		self.level.clearDistances()
		self.level.growDistancesFrom( self.x, self.y )
		self.level.growDistancesFrom( self.x, self.y, alternate = True )
	def generateFov(self):
		self.fov = Fov( self.vision )
		self.fov.initialize360( self.x, self.y )
		self.fov.spread()
		self.viewport.maskVision( self.fov )
		for x in range(0,levelWidth):
			for y in range(0,levelHeight):
				if self.fov.isLit(x,y):
					self.level.tiles[x][y].remembered = True
	def damageMessage(self, amount):
		if cheatImmortality: # hack!
			self.hp += amount
		self.game.interface.messages.add( "You take <RED>%d</RED> damage!" % amount )
	def setMeleeCapability(self):
		if self.readiedItem:
			meleeCap = getattr( self.readiedItem.itemType, "meleeCapability", None )
			itN = self.readiedItem.commonName()
			if meleeCap:
				self.meleeAttack = ( "You attack the %%s with your %s!" % itN, "You attack the %%s with your %s, but miss!" % itN ,) + meleeCap
			else:
				self.meleeAttack = ( "You take a swing at the %%s with your %s!" % itN, "You try to hit the %%s with your %s, but miss!" % itN , 0.5, 10 )
		else:
			self.meleeAttack = ( "You attack the %s with your bare fists.", "You swing at the %s, but miss!", 0.5, 5 )
	def readyItem(self, item, silent = False):
		if item and self.readiedItem and self.readiedItem.sameItem( item ):
			if not silent:
				self.game.interface.messages.add( "You're already holding your %s." % item.commonName() )
			return False
		if self.readiedItem:
			if not silent and item:
				self.game.interface.messages.add( "You put away your %s." % self.readiedItem.commonName() )
			self.readiedItem.controller = None
		self.readiedItem = item
		if item:
			item.controller = self
			if not silent:
				self.game.interface.messages.add( "You draw your %s from your belt." % item.commonName() )
		self.setMeleeCapability()
		return True
	def die(self):
		raise PlayerDead()

		
class Timekeeper:
	serializedAttributes = [
		"nonplayers",
		"game"
	]
	def __init__(self, game):
		self.nonplayers = []
		self.game = game
	def tick(self):
		self.game.interface.refresh()
		self.game.resetAllDevices()
		random.shuffle( self.nonplayers )
#		for i in range(len(self.nonplayers)):
		for nonplayer in self.nonplayers:
			if not nonplayer in self.nonplayers: continue
			if nonplayer.frozenCountdown <= 0:
				nonplayer.act()
			nonplayer.tick()
#			self.nonplayers[i].act()
#			self.nonplayers[i].tick()
#		self.game.interface.refresh()
		self.game.tickAllDevices()
		self.game.interface.clearRepeats()


class Game:
	serializedAttributes = [
		"at",
		"timekeeper",
		"levels",
		"version",
		"gtt",
		"allDevices",
		"totalLevels",
		"nextDeviceId"
	]
	def __init__(self, version, interface, playerName):
		# Creating a new game; this was done in enginerl before.
		self.allDevices = []
		self.gtt = TileBrushes()
		self.version = version
		self.interface = interface
		self.totalLevels = 9
		self.timekeeper = Timekeeper( self )
		self.levels = [ self.generateLevel( 0 ) ]
		firstLevel = self.levels[0]
#		self.interface.elements["map"].level = firstLevel
		self.at = PlayerCharacter( playerName, firstLevel, self)
		self.interface.elements["map"].mobile = self.at
		self.mainInputHandler = MainInputHandler( self, self.at, self.timekeeper )
		self.nextDeviceId = IdGenerator()
	def spawnRobot(self, level, difficulty, position = None):
		random.choice( Robots[difficulty] ).spawn( self, level, position )
	def resetAllDevices( self ):
		for device in self.allDevices:
			device.reset()
	def tickAllDevices( self ):
		random.shuffle( self.allDevices )
		for device in self.allDevices:
			device.tick()
	def generateLevel(self, n):
		from erldungeon import LevelGenerator, DisconnectedDungeon
		while True:
			try:
				level = Level( levelWidth, levelHeight, self.gtt.rock )
				LevelGenerator( level, self.gtt )
				break
			except DisconnectedDungeon:
				pass
		x, y = None, None
		x, y = level.getRandomTileOfType( self.gtt.floor )
		level.tiles[x][y] = self.gtt.stairsUp.spawn()
		if (n+1) < self.totalLevels:
			l = []
			if x and y:
				for i in range(10):
					nx, ny = level.getRandomTileOfType( self.gtt.floor )
					l.append( ( (nx-x)*(nx-x) + (ny-y)*(ny-y), (nx,ny) ) )
				l.sort()
				x, y = l[-1][1]
			else:
				x, y = level.getRandomTileOfType( self.gtt.floor )
			level.tiles[x][y] = self.gtt.stairsDown.spawn()
		for i in range( max(0, random.randint(-1,6) ) * plentyMultiplier ):
			x, y = level.getRandomTileOfType( self.gtt.floor )
			item = random.choice( ItemTable.RandomMisc ).spawn()
			level.putItem( item, x, y )
		if (n+1) == self.totalLevels:
			for i in range(20):
				self.spawnRobot( level, random.randint( 1, 4 ) )
			self.spawnRobot( level, 5 )
		else:
			monLevs = min( 3, n // 2 ) + 1
			noMons = random.randint(n,2*n) // 2
			for i in range( 5 ):
				self.spawnRobot( level, monLevs )
			for i in range(noMons):
				monLev = random.randint( 1, monLevs )
				self.spawnRobot( level, monLev )
		return level
	def frozenMessages(self):
		if self.at.frozenCountdown > 0:
			if self.at.frozenCountdown > 1:
				noTurns = "%d turns left" % self.at.frozenCountdown
			else:
				noTurns = "1 turn left"
			self.interface.messages.add( "<BLUE>You are frozen in place, unable to move! </BLUE> (%s. Use the wait key to continue.)" % noTurns )
	def gameloop(self):
		highlighter = Highlighter(self.at, self.interface)
		self.interface.postProcessors.append( highlighter )
		try:
			while True:
				self.frozenMessages()
				self.interface.messages.show()
				self.interface.refresh()
				self.interface.waitForInput()
		except NoInputHandler:
			pass
		self.interface.postProcessors.remove( highlighter )
	def save(self, filename):
		import time
		print "Saving game...",
		serial = Serializator( serializableClasses, serializablePointers )
		handle = GzipFile( filename, "w" )
		t0 = time.time()
		serial.write( handle , self.version, self )
		print "done. Took %0.2lf seconds." % (time.time() - t0)
		handle.close()
		self.interface.clearRepeats()
	def linkInterface(self, interface):
		self.interface = interface
		self.at.viewport = interface.elements["map"]
		self.at.vision = MobileVision( self.at, radius = None )
		self.at.generateFov()
#		self.at.viewport.level = self.at.level
		self.at.viewport.mobile = self.at
		self.interface.elements["info"].player = self.at
		self.mainInputHandler = MainInputHandler( self, self.at, self.timekeeper )
	def __deepcopy__(self, memo):
		return self
	def getReportName(self, mobile):
		if mobile == self.at:
			return "you"
		return mobile.name
	def reportAttack(self, attacker, attackee, message ):
		message = message % self.getReportName( attackee )
		if self.at.fov.isLit( attacker.x, attacker.y ) or self.at.fov.isLit( attackee.x, attackee.y ):
			self.interface.messages.add( message )
	def getLevelIndex(self, level): #hack
		try:
			return self.levels.index( level )
		except ValueError:
			return None
	def getLevelAbove(self, level):
		li = self.getLevelIndex( level )
		if li == 0:
			return None
		return self.levels[li-1]
	def getLevelBelow(self, level):
		li = self.getLevelIndex( level )
		while (li+1) >= len( self.levels ):
			self.levels.append( self.generateLevel( len( self.levels ) ) )
			self.interface.clearRepeats()
		return self.levels[li+1]


def loadGame( version, filename, interface ):
	serial = Serializator( serializableClasses, serializablePointers )
	handle = GzipFile( filename, "r" )
	game = serial.read( handle, version )
	handle.close()
	game.linkInterface( interface )
	return game
	
serializableClasses += [
	Mobile, Glyph, Tile, TileType, Level, Inventory, ItemStack,
	ItemType, ItemUnit, Noun, ItemTypeUnique, HandheldDevice,
	IdGenerator, Device, Component, Chassis, PlayerCharacter,
	NonPC, Timekeeper, Game, TileBrushes
]

Glyph.serializedAttributes = [
	"char",
	"fgColour",
	"bgColour"
]


TileType.serializedAttributes = [
	"name",
	"traversibility",
	"obstruction",
	"glyph"
]

Level.serializedAttributes = [
	"width",
	"height",
	"tiles"
]

ItemUnit.serializedAttributes = [
	"name",
	"decimals"
]

Noun.serializedAttributes = [
	"indefiniteArticle",
	"singular",
	"plural"
]

IdGenerator.serializedAttributes = [
	"next"
]

Chassis.serializedAttributes = [
	"shellClass",
	"description",
	"shortDescription",
	"bulkLimit",
	"weightLimit"
]


